/*
 * Copyright 2012 Hannes Janetzek
 *
 * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.oscim.renderer.bucket;

import org.oscim.renderer.bucket.VertexData.Chunk;
import org.oscim.utils.FastMath;
import org.oscim.utils.pool.Inlist;
import org.oscim.utils.pool.SyncPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ShortBuffer;

/**
 * A linked list of array chunks to hold temporary vertex data.
 * <p/>
 * TODO override append() etc to update internal (cur) state.
 */
public class VertexData extends Inlist.List<Chunk> {
    static final Logger log = LoggerFactory.getLogger(VertexData.class);

    /**
     * Size of array chunks. Must be multiple of:
     * 4 (LineLayer/PolygonLayer),
     * 24 (TexLineLayer - one block, i.e. two segments)
     * 24 (TextureLayer)
     */
    public static final int SIZE = 360;

    /**
     * Shared chunk pool size.
     */
    private static final int MAX_POOL = 500;

    public static class Chunk extends Inlist<Chunk> {
        public final short[] vertices = new short[SIZE];
        public int used;
    }

    private static class Pool extends SyncPool<Chunk> {
        public Pool() {
            super(MAX_POOL);
        }

        @Override
        protected Chunk createItem() {
            return new Chunk();
        }

        @Override
        protected boolean clearItem(Chunk it) {
            it.used = 0;
            return true;
        }
    }

    public int countSize() {
        if (cur == null)
            return 0;

        cur.used = used;

        int size = 0;
        for (Chunk it = head(); it != null; it = it.next)
            size += it.used;

        return size;
    }

    @Override
    public Chunk clear() {
        if (cur == null)
            return null;

        cur.used = used;
        used = SIZE; /* set SIZE to get new item on add */
        cur = null;
        vertices = null;

        return super.clear();
    }

    private static final Pool pool = new Pool();

    public void dispose() {
        pool.releaseAll(super.clear());
        used = SIZE; /* set SIZE to get new item on add */
        cur = null;
        vertices = null;
    }

    /**
     * @return sum of elements added
     */
    public int compile(ShortBuffer sbuf) {
        if (cur == null)
            return 0;

        cur.used = used;

        int size = 0;
        for (Chunk it = head(); it != null; it = it.next) {
            size += it.used;
            sbuf.put(it.vertices, 0, it.used);
        }
        dispose();
        return size;
    }

    private Chunk cur;

    /* set SIZE to get new item on add */
    private int used = SIZE;

    private short[] vertices;

    private void getNext() {
        if (cur == null) {
            cur = pool.get();
            push(cur);
        } else {
            if (cur.next != null)
                throw new IllegalStateException("seeeked...");

            cur.used = SIZE;
            cur.next = pool.get();
            cur = cur.next;
        }
        vertices = cur.vertices;
        used = 0;
    }

    public void add(float a) {
        add(toShort(a));
    }

    public void add(short a) {
        if (used == SIZE)
            getNext();

        vertices[used++] = a;
    }

    public void add(float a, float b) {
        add(toShort(a), toShort(b));
    }

    public void add(short a, short b) {
        if (used == SIZE)
            getNext();

        vertices[used + 0] = a;
        vertices[used + 1] = b;
        used += 2;
    }

    public void add(float a, float b, float c) {
        add(toShort(a), toShort(b), toShort(c));
    }

    public void add(short a, short b, short c) {
        if (used == SIZE)
            getNext();

        vertices[used + 0] = a;
        vertices[used + 1] = b;
        vertices[used + 2] = c;
        used += 3;
    }

    public void add(float a, float b, float c, float d) {
        add(toShort(a), toShort(b), toShort(c), toShort(d));
    }

    public void add(short a, short b, short c, short d) {
        if (used == SIZE)
            getNext();

        vertices[used + 0] = a;
        vertices[used + 1] = b;
        vertices[used + 2] = c;
        vertices[used + 3] = d;
        used += 4;
    }

    public void add(float a, float b, float c, float d, float e, float f) {
        add(toShort(a), toShort(b), toShort(c), toShort(d), toShort(e), toShort(f));
    }

    public void add(short a, short b, short c, short d, short e, short f) {
        if (used == SIZE)
            getNext();

        vertices[used + 0] = a;
        vertices[used + 1] = b;
        vertices[used + 2] = c;
        vertices[used + 3] = d;
        vertices[used + 4] = e;
        vertices[used + 5] = f;
        used += 6;
    }

    public boolean empty() {
        return cur == null;
    }

    /**
     * Direct access to the current chunk of VertexData. Use with care!
     * <p/>
     * When changing the position use releaseChunk to update internal state
     */
    public Chunk obtainChunk() {
        if (used == SIZE)
            getNext();

        cur.used = used;

        return cur;
    }

    public void releaseChunk() {
        used = cur.used;
    }

    public void releaseChunk(int size) {
        cur.used = size;
        used = size;
    }

    /**
     * Do not use!
     */
    public void seek(int offset) {
        used += offset;
        cur.used = used;

        if (used > SIZE || used < 0)
            throw new IllegalStateException("seeked too far: " + offset + "/" + used);
    }

    static final short toShort(float v) {
        return (short) FastMath.clamp(v, Short.MIN_VALUE, Short.MAX_VALUE);
    }
}
